package edu.cmsc611.textsimulator;
import java.util.*;
import java.awt.*;
import java.lang.*;

/**
 * The Instruction class is used by the simulator to
 * hold all information needed for an instruction.
 * As of 8/26/97 this includes branch prediction
 * although it is not yet implemented.
 *
 *
Type	-31-    format (bits)                                 -0-
R	opcode (6)	rs (5)	rt (5)	rd (5)	shamt (5)	funct (6)
I	opcode (6)	rs (5)	rt (5)	immediate (16)
J	opcode (6)	address (26)

 */
public class Instruction
{
	//public static final Object regType = null;
	/**
	 * opcode stores an integer value for an instruction's opcode.
	 * These integer values were determined arbitrarily and are
	 * defined in the constructor.
	 */
	public int opcode;
	/**
	 * rs hold the source1 register number.
	 */
	public int rs;
	/**
	 * rt holds the source2 register number.
	 */
	public int rt;
	/**
	 * rd holds the destination register number.
	 */
	public int rd;
	/**
	 * immediate holds the immediate value for I-type instructions.
	 */
	public int immediate;
	/**
	 * rdValue holds the value stored in the register specified by rd.
	 */
	public int rdValue;
	/**
	 * rsValue holds the value stored in the register specified by rs.
	 */
	public int rsValue;
	/**
	 * rtValue holds the value stored in the register specified by rt.
	 */
	public int rtValue;
	/**
	 * instructionString is a String representation of the instruction.
	 */
	public String instructionString;
	/**
	 * ok is an internal boolean used for checking the validity of an entered instruction.
	 */
	private boolean ok;
	/**
	 * flush indicates if the instruction has been flushed.
	 */
	public boolean flush;
	/**
	 * branchTaken indicates if a branch instruction was taken last time (currently unused).
	 */
	public boolean branchTaken;
	/**
	 * wbFlush indicates if an instruction has been flushed.  This additional flush variable was
	 * included because of the case when an instruction is in WriteBack as well as Fetch.  This
	 * case only occurs for an instruction following a branch in a one instruction loop.
	 */
	public boolean wbFlush;
	/**
	 * used to indicate that the Rs value of a previous instruction is needed before this instruction
	 * can continue execution.
	 */
	public boolean forwardRsFlag;
	/**
	 * used to indicate that the Rt value of a previous instruction is needed before this instruction
	 * can continue execution.
	 */
	public boolean forwardRtFlag;
	/**
	 * indicates whether the instruction is an I-type instruction (and that the Rt value in not needed).
	 */
	private boolean isImmediateInstruction;
	/**
	 * internal string used to indicate an error with an entered instruction.
	 */
	private String error = "The entered instruction is invalid.";
    /**
     * keep the value of loop
     */
	private String loopLabel;

	private String loopStartLabel=null;
	/**
	 * takes a string and parses it to fill the fields of the object.
	 */
    public static enum regType{R,F,O};

    regType registerType;
    /*
     * keep track of instruction for final print
     */
    int instruction_index;
    /*
     * cycles elapse to current stage
     */
    int cycleElapse=0;
    /**
     * keep track of which iteration this instruction was submited to MIPS
     */

    int iteration_index=-1;
    
    
	/**
     * rdtype
     */
    regType rdtype;
    regType rttype;
    regType rstype;
	/**
	 * @return the cycleElapse
	 */
	public int getCycleElapse() {
		return cycleElapse;
	}

	/**
	 * @param cycleElapse the cycleElapse to set
	 */
	public void setCycleElapse(int cycleElapse) {
		this.cycleElapse = cycleElapse;
	}

	public int getIteration_index() {
		return iteration_index;
	}

	public void setIteration_index(int iteration_index) {
		this.iteration_index = iteration_index;
	}

	Instruction(String temp)
	{
		StringTokenizer tokens = new StringTokenizer(temp, ",");
		String op = "", t1 = "", t2 = "", t3 = "";
	//	String loopLabel="";
		int switcher;

		rdValue = 0;              // initial operand values
		rsValue = 0;
		rtValue = 0;
		rstype=regType.O;
		rdtype=regType.O;
		rttype=regType.O;

		loopLabel=null;
		this.loopStartLabel=null;
		isImmediateInstruction = false;  // assumes no immediate operand
		ok = true;                // instruction is recognized by parser
		instructionString = temp; // the full instruction as a string
		flush = false;            // instruction will be executed
		branchTaken = true;       // assume branch will be taken initially,
		//   if this instruction is a branch
		forwardRsFlag = false;    // assume not forwarded data
		forwardRtFlag = false;
		instruction_index=0;

		// if no-op instruction (aka a stall)
		if (temp.equals("NOP")) {
			instructionString = "NOP";
			rd = 0;
			rt = 0;
			rs = 0;
			rstype=regType.O;
			rdtype=regType.O;
			rttype=regType.O;
			immediate = 0;
			opcode = 0;
			return;
		}

		trying:
			try
		{
				/*
				 * consider of loading first three token and keep checking for 4th one if its not LW or SW
				 */
				String beforfirstcomma = tokens.nextToken();
				/*
				 * check if the opcode included label, if so remove and store label with program counter
				 * for time being discard the label and do only for single iteration
				 */
				if(beforfirstcomma.contains(":"))
				{
					this.loopStartLabel=beforfirstcomma.substring(0,beforfirstcomma.indexOf(':'));


					beforfirstcomma=beforfirstcomma.substring(beforfirstcomma.indexOf(':')+1);
				}
				StringTokenizer firstparttokens = new StringTokenizer(beforfirstcomma, " ");
				op=firstparttokens.nextToken().trim();

				/*
				 * keep the register type information with the instruction
				 */
				if(op.startsWith("F"))
					registerType=regType.F;
				else if(op.startsWith("R"))
					registerType=regType.R;
				else registerType=regType.O;


				if(!op.equals("HLT"))
				t1 = firstparttokens.nextToken().trim();
				/*
				 * if its jump, ins is J LABEL, so only two tokens
				 */
				if(!op.startsWith("J") && !op.equals("HLT") )
				  t2 = tokens.nextToken().trim();

				/*
				 * Token or register representation must be grater than 2 in length
				 * like $1,$R $F LW, ADD, SW
				 */
				if(!op.equals("HLT")){
				if((t1.length() < 2 || t2.length() < 2) )
				{
					ok = false;
					break trying;
				}
				}

				/*
				 * if not LW or SW, then must have third parameter value
				 */
				if (!op.equals("LW") && !op.equals("SW")&& !op.equals("L.D") && !op.equals("S.D")&& !op.equals("HLT"))
				{
					t3 = tokens.nextToken().trim();
					if(t3.length() == 0)
					{
						ok = false;
						break trying;
					}
				}

				/*
				 * supported opcode
				 * switch value-
				 * 0 - for normal opcode
				 * 1 -for immediate
				 * 2 - for lw and sw
				 * 3 -for branch condition
				 */
				if(	op.equals("AND") || op.equals("OR") ||
					op.equals("DADD")||	op.equals("DSUB")|| op.equals("ADD.D")||
					op.equals("MUL.D")||op.equals("DIV.D")|| op.equals("SUB.D"))
					switcher = 0;  // R-type instructions
				else if(op.equals("ORI") ||
						op.equals("ANDI")||
						op.equals("DADDI")||
						op.equals("DSUBI"))
					switcher = 1;  // I-type instructions
				else if(op.equals("LW") || op.equals("SW")|| op.equals("L.D")|| op.equals("S.D"))
					switcher = 2;  // load/store word instructions
				else if(op.equals("BEQ") || op.equals("BNE"))
					switcher = 3;  // branch instructions
				else if(op.equals("J")||op.equals("HLT"))//unconditional jump
					switcher = 4;
				else
				{
					ok = false;
					break trying;
				}

				switch (switcher)
				{
				case 0:
					if((!t1.startsWith("R") || !t2.startsWith("R") ||
							!t1.startsWith("R"))&& (!t1.startsWith("F") || !t2.startsWith("F") ||
									!t1.startsWith("F")))
					{
						ok = false;
						break trying;
					}
					else
					{
						rd = Integer.parseInt(t1.substring(1));
						rdtype=(t1.substring(0,1).equals("R"))?regType.R:regType.F;
						rs = Integer.parseInt(t2.substring(1));
						rstype=(t2.substring(0,1).equals("R"))?regType.R:regType.F;
						rt = Integer.parseInt(t3.substring(1));
						rttype=(t3.substring(0,1).equals("R"))?regType.R:regType.F;
						
						//System.out.println(this.instructionString+" register types=rdtype="+rdtype+"rstype="+rstype+"rttype="+rttype);

						if (rt < 0 || rt > 31 || rs < 0 || rs > 31 || rd < 1 || rd > 31)
						{
							error = "Invalid Instr: register out of bounds";
							ok = false;
							break trying;
						}
						if (op.equals("AND"))
							opcode = 32;
						else if (op.equals("OR"))
							opcode = 34;
						else if (op.equals("DSUB"))
							opcode = 24;
						else if (op.equals("DADD"))
							opcode = 26;
						else if (op.equals("ADD.D"))
							opcode = 36;
						else if (op.equals("MUL.D"))
							opcode = 38;
						else if (op.equals("DIV.D"))
							opcode = 50;
						else if (op.equals("SUB.D"))
							opcode = 51;
					}
					break;
				case 1:
					if((!t1.startsWith("R") || !t2.startsWith("R"))&& (!t1.startsWith("F") || !t2.startsWith("F")))
					{
						ok = false;
						break trying;
					}
					else
					{
						rd = Integer.parseInt(t1.substring(1));
						rs = Integer.parseInt(t2.substring(1));
						rdtype=(t1.substring(0,1).equals("R"))?regType.R:regType.F;
						rstype=(t2.substring(0,1).equals("R"))?regType.R:regType.F;
						if (rd < 1 || rd > 31 || rs < 0 || rs > 31)
						{
							error = "Invalid Instr: register out of bounds";
							ok = false;
							break trying;
						}
						isImmediateInstruction = true;
						immediate = Integer.parseInt(t3);
						if(op.equals("DADDI"))
							opcode = 40;
						else if (op.equals("DSUBI"))
							opcode = 42;
						else if (op.equals("ANDI"))
							opcode = 44;
						else if (op.equals("ORI"))
							opcode = 46;

					}
					break;
				case 2:
					if(!t1.startsWith("R") && !t1.startsWith("F"))
					{
						ok = false;
						break trying;
					}
					else
					{
						/*
						 * read instruction for rt and rs
						 * LW F6 0(R2)
						 */
						rd = Integer.parseInt(t1.substring(1));
						rs = Integer.parseInt(t2.substring(0,t2.indexOf("(")));
						rt = Integer.parseInt(t2.substring(t2.indexOf("R")+1,t2.indexOf(")")));

						rdtype=(t1.substring(0,1).equals("R"))?regType.R:regType.F;
						//rttype=(t1.substring(0,1)=="R")?regType.R:regType.F;
						rttype=regType.R;

						if (rt < 1 || rt > 31 || rs < 0 || rs > 31)
						{
							error = "Invalid Instr: register out of bounds";
							ok = false;
							break trying;
						}
						immediate = Integer.parseInt(t2.substring(0,t2.indexOf("(")).trim());
						if (op.equals("LW")) {
							opcode = 48;
							//rd = rt;
						}
						else if (op.equals("SW")) {
							opcode = 54;
							//rd = rt;
						}
						else if (op.equals("L.D")) {
							opcode = 56;
							//rd = rt;
						}
						else if(op.equals("S.W"))
							//rd=rt;
							opcode = 58; // SW is the only one left
					}
					break;
				case 3:
					if((!t1.startsWith("R") || !t2.startsWith("R"))&&(!t1.startsWith("F") || !t2.startsWith("F")))
					{
						ok = false;
						break trying;
					}
					else
					{
						rd = Integer.parseInt(t1.substring(1));
						rdtype=(t1.substring(0,1).equals("R"))?regType.R:regType.F;
						rs = Integer.parseInt(t2.substring(1));
						rstype=(t2.substring(0,1).equals("R"))?regType.R:regType.F;
						if (rs < 1 || rs > 31 || rt < 0 || rt > 31)
						{
							error = "Invalid Instr: register out of bounds";
							ok = false;
							break trying;
						}
						//isImmediateInstruction = false;//differentiat whether the number is immediate or loop label
						//immediate = Integer.parseInt(t3.trim());
						loopLabel=t3.trim();

						if(op.equals("BEQ"))
							opcode = 70;
						else if(op.equals("BNE"))
							opcode = 71; // BNE is the only one left
					}
					break;
				case 4:
//					if((!t1.startsWith("R") )
//					{
//						ok = false;
//						break trying;
//					}
//					else
//					{
//						rs = Integer.parseInt(t1.substring(1));
//						rt = Integer.parseInt(t2.substring(1));
//						if (rs < 1 || rs > 31 || rt < 0 || rt > 31)
//						{
//							error = "Invalid Instr: register out of bounds";
//							ok = false;
//							break trying;
//						}

						if(op.equals("J"))
						{
							isImmediateInstruction = false;
							loopLabel=t1.trim();//immediate = Integer.parseInt(t3.trim());
							opcode = 81;
						}
						else if (op.equals("HLT"))
							opcode = 82;
						 // BNE is the only one left
					//}
					break;
				default:
					ok = false;
					break trying;
				}
		}
		catch(NumberFormatException e)
		{
			ok = false;
		}
		catch(NoSuchElementException e2)
		{
			ok = false;
		}
		catch(StringIndexOutOfBoundsException e3)
		{
			ok = false;
		}
	}

	/**
	 * @return the loopLabel
	 */
	public String getLoopLabel() {
		return loopLabel;
	}

	/**
	 * @param loopLabel the loopLabel to set
	 */
	public void setLoopLabel(String loopLabel) {
		this.loopLabel = loopLabel;
	}

	public int getInstruction_index() {
		return instruction_index;
	}

	public void setInstruction_index(int instruction_index) {
		this.instruction_index = instruction_index;

		if(this.loopStartLabel!=null){
		Utilities utils=Utilities.getInstance();
		utils.put(loopStartLabel, instruction_index);
		}
	}

	/**
	 * returns a boolean indicating whether the instruction is valid.
	 */
	public boolean valid()
	{
		return ok;
	}

	/**
	 * @return the rdtype
	 */
	public regType getRdtype() {
		return rdtype;
	}

	/**
	 * @param rdtype the rdtype to set
	 */
	public void setRdtype(regType rdtype) {
		this.rdtype = rdtype;
	}

	/**
	 * @return the rttype
	 */
	public regType getRttype() {
		return rttype;
	}

	/**
	 * @param rttype the rttype to set
	 */
	public void setRttype(regType rttype) {
		this.rttype = rttype;
	}

	/**
	 * @return the rstype
	 */
	public regType getRstype() {
		return rstype;
	}

	/**
	 * @param rstype the rstype to set
	 */
	public void setRstype(regType rstype) {
		this.rstype = rstype;
	}

	/**
	 * returns a string indicating the error generated from the entered instruction.
	 */
	public String theError()
	{
		return error;
	}

	/**
	 * returns a boolean indicating whether the instruction is an I-type.
	 */
	public boolean isImmediate()
	{
		return isImmediateInstruction;
	}

	/**
	 * returns a string representation of the instruction.
	 */
	public String toString()
	{
		return (instructionString+"\n");
	}
}
